import requests
import json
import logging
from typing import Dict, List, Optional, Any
from urllib.parse import urljoin
import base64
from django.utils import timezone

logger = logging.getLogger(__name__)


class JenkinsAPIManager:
    """Jenkins API管理器"""

    def __init__(self, server_url: str, username: str, api_token: str):
        self.server_url = server_url.rstrip('/')
        self.username = username
        self.api_token = api_token
        self.session = requests.Session()

        # 设置认证
        auth_string = f"{username}:{api_token}"
        auth_bytes = auth_string.encode('ascii')
        auth_b64 = base64.b64encode(auth_bytes).decode('ascii')
        self.session.headers.update({
            'Authorization': f'Basic {auth_b64}',
            'Content-Type': 'application/json'
        })

    def test_connection(self) -> Dict[str, Any]:
        """测试Jenkins连接"""
        try:
            url = f"{self.server_url}/api/json"
            response = self.session.get(url, timeout=10)

            if response.status_code == 200:
                data = response.json()
                # Jenkins版本在HTTP头中
                version = response.headers.get('X-Jenkins', 'Unknown')
                return {
                    'success': True,
                    'message': '连接成功',
                    'version': version,
                    'node_name': data.get('nodeName', ''),
                    'num_executors': data.get('numExecutors', 0)
                }
            elif response.status_code == 401:
                return {
                    'success': False,
                    'message': '认证失败，请检查用户名和API Token'
                }
            elif response.status_code == 403:
                return {
                    'success': False,
                    'message': '权限不足，请检查用户权限'
                }
            else:
                return {
                    'success': False,
                    'message': f'连接失败，状态码: {response.status_code}'
                }

        except requests.exceptions.ConnectTimeout:
            return {
                'success': False,
                'message': '连接超时，请检查Jenkins服务器地址'
            }
        except requests.exceptions.ConnectionError:
            return {
                'success': False,
                'message': '连接错误，无法访问Jenkins服务器'
            }
        except Exception as e:
            logger.error(f"Jenkins连接测试失败: {str(e)}")
            return {
                'success': False,
                'message': f'连接测试失败: {str(e)}'
            }

    def get_jobs(self) -> List[Dict[str, Any]]:
        """获取所有任务列表"""
        try:
            url = f"{self.server_url}/api/json?tree=jobs[name,url,color,lastBuild[number,timestamp,result,duration]]"
            response = self.session.get(url, timeout=30)

            if response.status_code == 200:
                data = response.json()
                jobs = []

                for job in data.get('jobs', []):
                    job_info = {
                        'name': job.get('name'),
                        'url': job.get('url'),
                        'color': job.get('color'),
                        'status': self._parse_job_status(job.get('color')),
                        'lastBuild': None
                    }

                    # 解析最后构建信息
                    last_build = job.get('lastBuild')
                    if last_build:
                        job_info['lastBuild'] = {
                            'number': last_build.get('number'),
                            'timestamp': last_build.get('timestamp'),
                            'result': last_build.get('result'),
                            'duration': last_build.get('duration')
                        }

                    jobs.append(job_info)

                return jobs
            else:
                logger.error(f"获取Jenkins任务列表失败: {response.status_code}")
                return []

        except Exception as e:
            logger.error(f"获取Jenkins任务列表异常: {str(e)}")
            return []

    def get_job_details(self, job_name: str) -> Optional[Dict[str, Any]]:
        """获取任务详细信息"""
        try:
            url = f"{self.server_url}/job/{job_name}/api/json"
            response = self.session.get(url, timeout=30)

            if response.status_code == 200:
                return response.json()
            else:
                logger.error(f"获取Jenkins任务详情失败: {response.status_code}")
                return None

        except Exception as e:
            logger.error(f"获取Jenkins任务详情异常: {str(e)}")
            return None

    def get_job_config(self, job_name: str) -> Optional[str]:
        """获取任务配置XML"""
        try:
            url = f"{self.server_url}/job/{job_name}/config.xml"
            response = self.session.get(url, timeout=30)

            if response.status_code == 200:
                return response.text
            else:
                logger.error(f"获取Jenkins任务配置失败: {response.status_code}")
                return None

        except Exception as e:
            logger.error(f"获取Jenkins任务配置异常: {str(e)}")
            return None

    def trigger_build(self, job_name: str, parameters: Optional[Dict] = None) -> Dict[str, Any]:
        """触发构建"""
        try:
            if parameters:
                url = f"{self.server_url}/job/{job_name}/buildWithParameters"
                response = self.session.post(url, data=parameters, timeout=30)
            else:
                url = f"{self.server_url}/job/{job_name}/build"
                response = self.session.post(url, timeout=30)

            if response.status_code in [200, 201]:
                # 获取下一个构建号
                import time
                time.sleep(1)  # 等待一秒让Jenkins处理请求

                job_details = self.get_job_details(job_name)
                if job_details:
                    next_build_number = job_details.get('nextBuildNumber', 1)
                    # 实际构建号是nextBuildNumber - 1
                    build_number = next_build_number - 1 if next_build_number > 1 else 1
                else:
                    build_number = 1

                return {
                    'success': True,
                    'message': '构建已触发',
                    'build_number': build_number
                }
            else:
                return {
                    'success': False,
                    'message': f'触发构建失败，状态码: {response.status_code}'
                }

        except Exception as e:
            logger.error(f"触发Jenkins构建异常: {str(e)}")
            return {
                'success': False,
                'message': f'触发构建失败: {str(e)}'
            }

    def get_build_info(self, job_name: str, build_number: int) -> Optional[Dict[str, Any]]:
        """获取构建信息"""
        try:
            url = f"{self.server_url}/job/{job_name}/{build_number}/api/json"
            response = self.session.get(url, timeout=30)

            if response.status_code == 200:
                return response.json()
            else:
                logger.error(f"获取Jenkins构建信息失败: {response.status_code}")
                return None

        except Exception as e:
            logger.error(f"获取Jenkins构建信息异常: {str(e)}")
            return None

    def get_build_console_output(self, job_name: str, build_number: int) -> Optional[str]:
        """获取构建控制台输出"""
        try:
            url = f"{self.server_url}/job/{job_name}/{build_number}/consoleText"
            response = self.session.get(url, timeout=30)

            if response.status_code == 200:
                return response.text
            else:
                logger.error(f"获取Jenkins构建日志失败: {response.status_code}")
                return None

        except Exception as e:
            logger.error(f"获取Jenkins构建日志异常: {str(e)}")
            return None

    def abort_build(self, job_name: str, build_number: int) -> Dict[str, Any]:
        """中止构建"""
        try:
            url = f"{self.server_url}/job/{job_name}/{build_number}/stop"
            response = self.session.post(url, timeout=30)

            if response.status_code in [200, 302]:
                return {
                    'success': True,
                    'message': '构建已中止'
                }
            else:
                return {
                    'success': False,
                    'message': f'中止构建失败，状态码: {response.status_code}'
                }

        except Exception as e:
            logger.error(f"中止Jenkins构建异常: {str(e)}")
            return {
                'success': False,
                'message': f'中止构建失败: {str(e)}'
            }

    def _parse_job_status(self, color: str) -> str:
        """解析任务状态"""
        if not color:
            return 'unknown'

        color = color.lower()
        if 'blue' in color:
            return 'success'
        elif 'red' in color:
            return 'failed'
        elif 'yellow' in color:
            return 'unstable'
        elif 'grey' in color or 'disabled' in color:
            return 'disabled'
        elif 'anime' in color:
            return 'building'
        else:
            return 'unknown'

    def create_job(self, job_name: str, config_xml: str) -> Dict[str, Any]:
        """创建Jenkins任务"""
        try:
            url = f"{self.server_url}/createItem?name={job_name}"
            headers = {'Content-Type': 'application/xml'}
            response = self.session.post(url, data=config_xml, headers=headers, timeout=30)

            if response.status_code in [200, 201]:
                return {
                    'success': True,
                    'message': '任务创建成功'
                }
            else:
                return {
                    'success': False,
                    'message': f'创建任务失败，状态码: {response.status_code}'
                }

        except Exception as e:
            logger.error(f"创建Jenkins任务异常: {str(e)}")
            return {
                'success': False,
                'message': f'创建任务失败: {str(e)}'
            }

    def get_build_console_output(self, job_name: str, build_number: int) -> Dict[str, Any]:
        """获取构建控制台输出"""
        try:
            # 获取构建详情
            build_url = f"{self.server_url}/job/{job_name}/{build_number}/api/json"
            build_response = self.session.get(build_url, timeout=30)

            if build_response.status_code != 200:
                return {
                    'success': False,
                    'message': f'获取构建信息失败，状态码: {build_response.status_code}',
                    'console_output': '',
                    'status': 'UNKNOWN'
                }

            build_data = build_response.json()

            # 获取控制台日志
            console_url = f"{self.server_url}/job/{job_name}/{build_number}/consoleText"
            console_response = self.session.get(console_url, timeout=30)

            if console_response.status_code != 200:
                return {
                    'success': False,
                    'message': f'获取控制台日志失败，状态码: {console_response.status_code}',
                    'console_output': '',
                    'status': 'UNKNOWN'
                }

            # 解析构建状态
            result = build_data.get('result', 'RUNNING')
            if result is None and build_data.get('building', False):
                result = 'RUNNING'

            # 计算持续时间
            duration = build_data.get('duration', 0)
            if duration == 0 and build_data.get('building', False):
                # 如果正在构建，计算已运行时间
                timestamp = build_data.get('timestamp', 0)
                if timestamp:
                    import time
                    duration = int((time.time() * 1000) - timestamp)

            return {
                'success': True,
                'console_output': console_response.text,
                'status': result,
                'duration': duration,
                'building': build_data.get('building', False),
                'timestamp': build_data.get('timestamp', 0)
            }

        except Exception as e:
            logger.error(f"获取构建控制台日志异常: {str(e)}")
            return {
                'success': False,
                'message': f'获取控制台日志失败: {str(e)}',
                'console_output': '',
                'status': 'ERROR'
            }

    def get_job_builds(self, job_name: str) -> Dict[str, Any]:
        """获取任务的所有构建记录"""
        try:
            # 获取任务详情，包含构建历史
            job_url = f"{self.server_url}/job/{job_name}/api/json?tree=builds[number,result,building,timestamp,duration]"
            response = self.session.get(job_url, timeout=30)

            if response.status_code != 200:
                return {
                    'success': False,
                    'message': f'获取任务构建历史失败，状态码: {response.status_code}',
                    'builds': []
                }

            job_data = response.json()
            builds = job_data.get('builds', [])

            return {
                'success': True,
                'builds': builds,
                'total': len(builds)
            }

        except Exception as e:
            logger.error(f"获取任务构建历史异常: {str(e)}")
            return {
                'success': False,
                'message': f'获取构建历史失败: {str(e)}',
                'builds': []
            }

    def update_job_config(self, job_name: str, config_xml: str) -> Dict[str, Any]:
        """更新Jenkins任务配置"""
        try:
            url = f"{self.server_url}/job/{job_name}/config.xml"
            headers = {'Content-Type': 'application/xml'}
            response = self.session.post(url, data=config_xml, headers=headers, timeout=30)

            if response.status_code == 200:
                return {
                    'success': True,
                    'message': '任务配置更新成功'
                }
            else:
                return {
                    'success': False,
                    'message': f'更新任务配置失败，状态码: {response.status_code}'
                }

        except Exception as e:
            logger.error(f"更新Jenkins任务配置异常: {str(e)}")
            return {
                'success': False,
                'message': f'更新任务配置失败: {str(e)}'
            }

    def delete_job(self, job_name: str) -> Dict[str, Any]:
        """删除Jenkins任务"""
        try:
            url = f"{self.server_url}/job/{job_name}/doDelete"
            response = self.session.post(url, timeout=30)

            if response.status_code in [200, 302]:
                return {
                    'success': True,
                    'message': '任务删除成功'
                }
            else:
                return {
                    'success': False,
                    'message': f'删除任务失败，状态码: {response.status_code}'
                }

        except Exception as e:
            logger.error(f"删除Jenkins任务异常: {str(e)}")
            return {
                'success': False,
                'message': f'删除任务失败: {str(e)}'
            }
